/*
 * Copyright (c) 2009, Hamish Morgan. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the University of Sussex nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package locusts.server.admin;

import java.net.SocketAddress;
import java.util.Date;
import java.util.Set;
import javax.naming.OperationNotSupportedException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.AbstractTableModel;
import locusts.client.ClientConfig;
import locusts.server.Server;
import org.apache.mina.common.IoHandler;
import org.apache.mina.common.IoService;
import org.apache.mina.common.IoServiceConfig;
import org.apache.mina.common.IoServiceListener;
import org.apache.mina.common.IoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A model for a JTable view to display a list of clients and their properties.
 * 
 * @author Hamish Morgan
 */
public class ClientsTableModel extends AbstractTableModel {

    private static final Logger LOG = LoggerFactory.getLogger(
            ClientsTableModel.class);
//    //
    private final Column addressColumn = new AddressColumn();
    private final Column creationTimeColumn = new CreationTimeColumn();
    private final Column bytesReadColumn = new BytesReadColumn();
    private final Column byteWrittenColumn = new BytesWrittenColumn();
    private final Column connectedColumn = new ConnectedColumn();
    private final Column xColumn = new XColumn();
    private final Column yColumn = new YColumn();
    private final Column widthColumn = new WidthColumn();
    private final Column heightColumn = new HeightColumn();
    private final Column scaleColumn = new ScaleColumn();
    private final Column rotateColumn = new RotateColumn();
    private final Column[] columnOrder = new Column[]{addressColumn,
        creationTimeColumn, bytesReadColumn, byteWrittenColumn,
        connectedColumn, xColumn, yColumn, widthColumn, heightColumn,
        scaleColumn, rotateColumn};
    private Server server;
    private final ClientConfigChangeHandler clientConfigChangeHandler =
            new ClientConfigChangeHandler();

    public ClientsTableModel(Server server) {
        this.server = server;
        server.getNetHandler().getAcceptor().addListener(
                new IoServiceHandler());
    }

    public class IoServiceHandler implements IoServiceListener {

        public void serviceActivated(IoService service,
                SocketAddress serviceAddress, IoHandler handler,
                IoServiceConfig config) {
//            throw new UnsupportedOperationException("Not supported yet.");
        }

        public void serviceDeactivated(IoService service,
                SocketAddress serviceAddress, IoHandler handler,
                IoServiceConfig config) {
//            throw new UnsupportedOperationException("Not supported yet.");
        }

        public void sessionCreated(final IoSession session) {
            new Thread(new Runnable() {

                public void run() {
                    try {
                        // The client config will be sent at some point in
                        // the future by the client - so we have to wait
                        // for that
                        while (server.getClientConfigs().get(session) == null)
                            Thread.sleep(100);
                        final ClientConfig cfg =
                                server.getClientConfigs().get(session);
                        final int row = getRowIndex(session);
                        fireTableRowsInserted(row, row);
//                        if (cfg == null) {
//                            cfg = new ClientConfig();
//                            server.getClientConfigs().put(session, cfg);
//                        }
                        cfg.addChangeListener(clientConfigChangeHandler);
                    } catch (InterruptedException ex) {
                        LOG.error(null, ex);
                    }
                }
            });
        }

        public void sessionDestroyed(IoSession session) {
            int row = getRowIndex(session);
            ClientConfig c = server.getClientConfigs().get(session);
            if(c != null)
                c.removeChangeListener(clientConfigChangeHandler);
            fireTableRowsDeleted(row, row);
        }
    }

    public class ClientConfigChangeHandler implements ChangeListener {

        public void stateChanged(ChangeEvent e) {
            int row = getRowIndex((ClientConfig) e.getSource());
            fireTableRowsUpdated(row, row);
        }
    }

    private int getRowIndex(ClientConfig cfg) {
        // find the row
        Object[] objs = server.getNetHandler().
                getManagedSessions().toArray();
        for (int i = 0; i < objs.length; i++) {
            if (!(objs[i] instanceof IoSession))
                continue;
            IoSession session = (IoSession) objs[i];
            if (server.getClientConfigs().containsKey(session)) {
                ClientConfig c = server.getClientConfigs().get(session);
                if (c == cfg)
                    return i;
            }
        }
        return -1;
    }

    private int getRowIndex(IoSession s) {
        // find the row
        Object[] objs = server.getNetHandler().
                getManagedSessions().toArray();
        for (int i = 0; i < objs.length; i++) {
            if (!(objs[i] instanceof IoSession))
                continue;
            IoSession session = (IoSession) objs[i];
            if (session == s)
                return i;
        }
        return -1;
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return columnOrder[columnIndex].isEditable();
    }

    public int getRowCount() {
        return server.getNetHandler().numManagedSessions();
    }

    public int getColumnCount() {
        return columnOrder.length;
    }

    public Object getValueAt(int rowIndex, int columnIndex) {
        try {
            Set<IoSession> ses = server.getNetHandler().getManagedSessions();
            IoSession s = (IoSession) (ses.toArray()[rowIndex]);
            return columnOrder[columnIndex].getValue(s);
        } catch (java.lang.IndexOutOfBoundsException e) {
            // this is a concurrency issue but should self correct
            return null;
        }
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        Set<IoSession> ses = server.getNetHandler().getManagedSessions();
        IoSession s = (IoSession) (ses.toArray()[rowIndex]);
        boolean updated = columnOrder[columnIndex].setValue(s, aValue);
        if (updated) {
            fireTableCellUpdated(rowIndex, columnIndex);
        }
    }

    @Override
    public String getColumnName(int column) {
        return columnOrder[column].getName();
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return columnOrder[columnIndex].getValueClass();
    }

    protected abstract static class Column {

        private String name;
        private Class<?> c;

        public Column(Class<?> c, String name) {
            this.name = name;
            this.c = c;
        }

        public String getName() {
            return name;
        }

        public boolean isEditable() {
            return true;
        }

        public Class<?> getValueClass() {
            return c;
        }

        public abstract Object getValue(IoSession p);

        public abstract boolean setValue(IoSession p, Object value);
    }

    protected abstract static class ReadOnlyColumn extends Column {

        public ReadOnlyColumn(Class<?> c, String name) {
            super(c, name);
        }

        @Override
        public boolean isEditable() {
            return false;
        }

        public boolean setValue(IoSession p, Object value) {
            throw new RuntimeException(new OperationNotSupportedException());
        }
    }

    protected class AddressColumn extends ReadOnlyColumn {

        public AddressColumn() {
            super(String.class, "Address");
        }

        @Override
        public Object getValue(IoSession s) {
            return s.getRemoteAddress();
        }
    }

    protected class CreationTimeColumn extends ReadOnlyColumn {

        public CreationTimeColumn() {
            super(Date.class, "Creation Time");
        }

        @Override
        public Object getValue(IoSession s) {
            return new Date(s.getCreationTime());
        }
    }

    protected class BytesReadColumn extends ReadOnlyColumn {

        public BytesReadColumn() {
            super(Long.class, "Bytes Read");
        }

        @Override
        public Object getValue(IoSession s) {
            return new Long(s.getReadBytes());
        }
    }

    protected class BytesWrittenColumn extends ReadOnlyColumn {

        public BytesWrittenColumn() {
            super(Long.class, "Bytes Written");
        }

        @Override
        public Object getValue(IoSession s) {
            return new Long(s.getWrittenBytes());
        }
    }

    protected class ConnectedColumn extends ReadOnlyColumn {

        public ConnectedColumn() {
            super(Boolean.class, "Connected");
        }

        @Override
        public Object getValue(IoSession s) {
            return new Boolean(s.isConnected());
        }
    }

    protected abstract class ConfigColumn extends Column {

        public ConfigColumn(Class<?> c, String name) {
            super(c, name);
        }

        @Override
        public Object getValue(IoSession s) {
            ClientConfig c = server.getClientConfigs().get(s);
            if (c == null)
                return null;
            else
                return getValue(c);
        }

        @Override
        public boolean setValue(IoSession s, Object value) {
            ClientConfig c = server.getClientConfigs().get(s);
            if (c == null)
                return false;
            else
                return setValue(c, value);
        }

        public abstract Object getValue(ClientConfig c);

        public abstract boolean setValue(ClientConfig p, Object value);
    }

    protected class XColumn extends ConfigColumn {

        public XColumn() {
            super(Double.class, "X");
        }

        @Override
        public Object getValue(ClientConfig c) {
            return new Double(c.getXTranslate());
        }

        @Override
        public boolean setValue(ClientConfig p, Object value) {
            final double newValue = (Double) value;
            if (newValue != p.getXTranslate()) {
                p.setXTranslate(newValue);
                return true;
            } else {
                return false;
            }
        }
    }

    protected class YColumn extends ConfigColumn {

        public YColumn() {
            super(Double.class, "Y");
        }

        @Override
        public Object getValue(ClientConfig c) {
            return new Double(c.getYTranslate());
        }

        @Override
        public boolean setValue(ClientConfig p, Object value) {
            final double newValue = (Double) value;
            if (newValue != p.getYTranslate()) {
                p.setYTranslate(newValue);
                return true;
            } else {
                return false;
            }
        }
    }

    protected class WidthColumn extends ConfigColumn {

        public WidthColumn() {
            super(Integer.class, "Width");
        }

        @Override
        public Object getValue(ClientConfig c) {
            return new Integer(c.getWidth());
        }

        @Override
        public boolean setValue(ClientConfig p, Object value) {
            final int newValue = (Integer) value;
            if (newValue != p.getWidth()) {
                p.setWidth(newValue);
                return true;
            } else {
                return false;
            }
        }
    }

    protected class HeightColumn extends ConfigColumn {

        public HeightColumn() {
            super(Integer.class, "Height");
        }

        @Override
        public Object getValue(ClientConfig c) {
            return new Integer(c.getHeight());
        }

        @Override
        public boolean setValue(ClientConfig p, Object value) {
            final int newValue = (Integer) value;
            if (newValue != p.getHeight()) {
                p.setHeight(newValue);
                return true;
            } else {
                return false;
            }
        }
    }

    protected class RotateColumn extends ConfigColumn {

        public RotateColumn() {
            super(Double.class, "Rotation");
        }

        @Override
        public Object getValue(ClientConfig c) {
            return new Double(c.getRotate());
        }

        @Override
        public boolean setValue(ClientConfig p, Object value) {
            final double newValue = (Double) value;
            if (newValue != p.getRotate()) {
                p.setRotate(newValue);
                return true;
            } else {
                return false;
            }
        }
    }

    protected class ScaleColumn extends ConfigColumn {

        public ScaleColumn() {
            super(Double.class, "Scale");
        }

        @Override
        public Object getValue(ClientConfig c) {
            return new Double(c.getScale());
        }

        @Override
        public boolean setValue(ClientConfig p, Object value) {
            final double newValue = (Double) value;
            if (newValue != p.getScale()) {
                p.setScale(newValue);
                return true;
            } else {
                return false;
            }
        }
    }
}
